#include "StreamBuffer.hpp"
#include "UnitTest.hpp"
#include "Util.hpp"
#include <stdlib.h>

// TODO: Modify this to allow empty space at the beginning of the buffer as
// well as the end, to avoid frequent expensive memmove()s (and making this
// O(n) instead of sometimes O(n^2)).
StreamBuffer::StreamBuffer()
{
	len = 0;
	alloc = 64;
	data = (char*)malloc(alloc);
}

StreamBuffer::~StreamBuffer()
{
	free(data);
}

void *StreamBuffer::reserveSpace(int appendLen)
{
	if(len+appendLen > alloc)
	{
		do {
			alloc *= 2;
		} while(len+appendLen > alloc);
		data = (char*)realloc(data, alloc);
	}
	
	return (void*)(data+len);
}

void StreamBuffer::appendReserved(int appendLen)
{
	len += appendLen;
}

void StreamBuffer::append(int appendLen, const char *appendData)
{
	if(len+appendLen > alloc)
	{
		do {
			alloc *= 2;
		} while(len+appendLen > alloc);
		data = (char*)realloc(data, alloc);
	}
	
	memcpy(data+len, appendData, appendLen);
	len += appendLen;
}

void StreamBuffer::popFront(int popSize)
{
	memmove(data, data+popSize, len-popSize);
	len -= popSize;
	
	while(alloc>4096 && alloc>3*len) {
		alloc /= 2;
		data = (char*)realloc(data, alloc);
	}
}

void StreamBuffer::clear()
{
	len = 0;
}

TEST_SET(StreamBuffer)
{
	int testSize = 65536; //64k
	char *testData = (char*)malloc(testSize);
	int writePos = 0;
	int readPos = 0;
	
	StreamBuffer buf;
	
	while(readPos<testSize || writePos<testSize)
	{
		int appendSize = min(testSize-writePos, randInt(1, 64));
		buf.append(appendSize, testData+writePos);
		writePos += appendSize;
		
		int popSize = min(buf.getLen(), randInt(1, 64));
		testAssert(!memcmp(buf.getData(), testData+readPos, popSize));
		buf.popFront(popSize);
		readPos += popSize;
	}
}

